﻿// Copyright (c) 2017 GlassGrass

using System;
using System.Collections.Generic;
using UnityEngine;



namespace GlassGrass.Unity.GuiFramework
	{
	/// <summary> Base class of Unity GUI element. </summary>
	public abstract class GuiElement : IBindable<string>
		{

		#region properties and fields

		#region framework system

		/// <summary> GUI config </summary>
		public GuiConfig Config { get; }

		#endregion



		#region layout parameters

		/// <summary> [get/set] If true then this element is visible. </summary>
		public bool Visible { get; set; } = true;

		/// <summary> [get/set] GUI element layouting relative location and size. </summary>
		public Rect Rect
			{
			get => _rect;
			set => _rect = value;
			}
		private Rect _rect;

		/// <summary> [get/set] GUI element layouting relative location. </summary>
		public virtual Vector2 Location
			{
			get => _rect.position;
			set => _rect.position = value;
			}

		/// <summary> [get/set] GUI element layouting size. </summary>
		public virtual Vector2 Size
			{
			get => _rect.size;
			set => _rect.size = value;
			}

		/// <summary> [get/set] GUI element layouting minimum size. </summary>
		public Vector2? MinimumSize { get; set; }
		
		/// <summary> [get/set] GUI element layouting maximum size. </summary>
		public Vector2? MaximumSize { get; set; }

		/// <summary> [get/set] Whether to allow element to resize automatically. </summary>
		public bool ResizableWidth { get; set; } = true;

		/// <summary> [get/set] Whether to allow element to resize height automatically. </summary>
		public bool ResizableHeight { get; set; } = true;

		/// <summary> [get] Auto layouting mode option. Read only list. </summary>
		protected GUILayoutOption[] LayoutOptions
			{
			get
				{
				var list = new List<GUILayoutOption>(8)
					{
					GUILayout.ExpandWidth(ResizableWidth),
					GUILayout.ExpandHeight(ResizableHeight),
					};
				if(!ResizableWidth)
					list.Add(GUILayout.Width(Size.x));
				if(!ResizableHeight)
					list.Add(GUILayout.Height(Size.y));
				if ( MinimumSize.HasValue )
					{
					list.Add(GUILayout.MinWidth(MinimumSize.Value.x));
					list.Add(GUILayout.MinHeight(MinimumSize.Value.y));
					}
				if( MaximumSize.HasValue )
					{
					list.Add( GUILayout.MaxWidth( MaximumSize.Value.x ) );
					list.Add( GUILayout.MaxHeight( MaximumSize.Value.y ) );
					}
				return list.ToArray();
				}
			}

		#endregion



		#region contents

		/// <summary> [get] GUI content </summary>
		public GUIContent Content { get; private set; } = new GUIContent();

		/// <summary> [get/set] Text of GUI content </summary>
		public virtual string Text
			{
			get => Content.text;
			set => Content.text = value;
			}

		/// <summary> [get/set] Image of GUI content </summary>
		public virtual Texture Image
			{
			get => Content.image;
			set => Content.image = value;
			}

		#endregion



		#region style parameters

		/// <summary><para> [get/set] GUI skin for itself and children </para>
		///          <para> NOTE: null-value will be inherited from parent skin.</para>
		/// </summary>
		public GUISkin CustomSkin { get; set; } = null;

		/// <summary><para> [get/set] GUI skin for itself and children </para>
		///          <para> NOTE: null-element will be inherited from parent skin.</para>
		/// </summary>
		public GUIStyle Style { get; set; } = null;

		#endregion

		#endregion



		#region construct & destruct

		/// <summary> Default constructor. </summary>
		/// <param name="config"> [Non-Nullable] </param>
		/// <exception cref="System.ArgumentNullException"></exception>
		protected GuiElement(GuiConfig config)
			{
			Config = config ?? throw new ArgumentNullException( nameof( config ) );
			}

		#endregion



		#region overridable methods

		/// <summary> Define what to choose style for this from skin. </summary>
		/// <param name="skin"></param>
		/// <returns></returns>
		protected abstract GUIStyle ChooseStyle(GUISkin skin);



		/// <summary> Define how to layout manually itself on layouting frame. </summary>
		/// <param name="parentSkin"></param>
		public abstract void LayoutManual(GUISkin parentSkin);



		/// <summary> Define how to layout automatically itself on layouting frame.  </summary>
		/// <param name="parentSkin"></param>
		public abstract void LayoutAuto(GUISkin parentSkin);



		/// <summary> Begin 'Text' property binding. </summary>
		/// <typeparam name="TValue"> Type of model property. </typeparam>
		/// <param name="modelObj"> Model object instance. </param>
		/// <param name="propertyName"> Model object property for binding. </param>
		/// <param name="converter"> Value converter. </param>
		/// <returns></returns>
		public virtual Binding<TValue, string> BeginBinding<TValue>(object modelObj, string propertyName, IValueConverter<TValue, string> converter = null)
			=> GuiBindingFactory.BeginBinding( modelObj, propertyName, this, nameof( Text ),converter );

		#endregion



		#region non-overridable methods

		/// <summary> Select valid skin. </summary>
		/// <param name="parentSkin"></param>
		/// <returns></returns>
		internal GUISkin SelectValidSkin(GUISkin parentSkin)
		=> CustomSkin ?? parentSkin ?? GUI.skin;



		/// <summary> Select valid stle. </summary>
		/// <param name="baseSkin"></param>
		/// <returns></returns>
		internal GUIStyle SelectValidStyle(GUISkin baseSkin)
			=> Style ?? ChooseStyle( baseSkin );

		#endregion

		}
	}
